#include <inttypes.h>
#include "duktape.h"

#define U16LE(buff) (uint16_t)(((buff[1] & 0xFF) << 8) | (buff[0] & 0xFF))
#define U32LE(buff) (uint32_t)(((buff[3] & 0xFF) << 24) | ((buff[2] & 0xFF) << 16) | ((buff[1] & 0xFF) << 8) | (buff[0] & 0xFF))

#define LEU16(buff, u)             \
    buff[0] = (uint8_t)(u & 0xFF); \
    buff[1] = (uint8_t)((u & 0xFF00) >> 8);

#define LEU32(buff, u)                         \
    buff[0] = (uint8_t)(u & 0xFF);             \
    buff[1] = (uint8_t)((u & 0xFF00) >> 8);    \
    buff[2] = (uint8_t)((u & 0xFF0000) >> 16); \
    buff[3] = (uint8_t)((u & 0xFF000000) >> 24);

typedef struct
{
    uint16_t opCode;
    uint32_t dataSize;
    uint8_t *data;
} bridge_packet_t;

typedef struct
{
    uint8_t magic;
    uint8_t resultType;
    uint32_t dataSize;
    uint8_t *data;
} bridgr_response_t;

typedef struct
{
    uint16_t uniqueId;
    uint32_t fileSize;
    uint16_t fileNameSize;
    char *fileName;
} file_header_t;

typedef struct
{
    uint16_t uniqueId;
    uint32_t offset;
    uint32_t size;
} file_data_t;

typedef struct file_record_node
{
    file_header_t header;
    struct file_record_node *next;
} file_record_node_t;

enum Opcode
{
    OP_CODE_MIN,

    OP_EXEC_CMD = 1,
    OP_SEND_FILE_HEADER,
    OP_SEND_FILE_PACKET,
    OP_SEND_FILE_END,

    OP_CODE_MAX
};

enum ResultCode
{
    RESULT_CODE_MIN = 0xF0,

    RESULT_EXEC_CMD = 0xF1,
    RESULT_FILE_HEADER,
    RESULT_FILE_END,

    RESULT_CODE_MAX,

    RESULT_MAGIC = 0xAB
};

bridge_packet_t *halBridgeFetchPacket(int32_t uart_num);
void halBridgeResponse(int32_t uart_num, bridgr_response_t *response);

file_record_node_t *recordRoot = NULL;

void printNodes()
{
    file_record_node_t *node = recordRoot->next;

    printf("\n=================\n");
    while (node->next != NULL)
    {
        printf("UID: %d\nfileSize: %d\nfileNameSize: %d\nfilename: %s\n----------\n",
               node->header.uniqueId,
               node->header.fileSize,
               node->header.fileNameSize,
               node->header.fileName);
        node = node->next;
    }
    printf("=================\n");
}

static void addRecord(file_header_t *header)
{
    file_record_node_t *node = (file_record_node_t *)malloc(sizeof(file_record_node_t));
    file_record_node_t *lastRecord = recordRoot;

    node->header.uniqueId = header->uniqueId;
    node->header.fileSize = header->fileSize;
    node->header.fileNameSize = header->fileNameSize;
    node->header.fileName = (char *)malloc(header->fileNameSize + 1);
    strcpy(node->header.fileName, header->fileName);
    node->next = NULL;

    if (recordRoot == NULL)
    {
        recordRoot = (file_record_node_t *)malloc(sizeof(file_record_node_t));
        recordRoot->next = node;
        return;
    }

    while (lastRecord->next != NULL)
    {
        lastRecord = lastRecord->next;
    }

    lastRecord->next = node;
}

static void removeRecord(uint16_t uniqueId)
{
    file_record_node_t *node;
    file_record_node_t *tmp = recordRoot;

    while (tmp->next != NULL)
    {
        if (tmp->next->header.uniqueId == uniqueId)
        {
            node = tmp->next;
            tmp->next = node->next;
            free(node->header.fileName);
            free(node);
            break;
        }
    }
}

file_record_node_t *searchRecord(uint16_t uniqueId)
{
    if (recordRoot == NULL)
        return NULL;

    file_record_node_t *node = recordRoot->next;
    while (node != NULL)
    {
        if (node->header.uniqueId == uniqueId)
            return node;
        node = node->next;
    }

    return NULL;
}

FILE *openRecord(uint16_t uniqueId)
{
    printf("Search: %d\n", uniqueId);
    file_record_node_t *node = searchRecord(uniqueId);

    printf("Search result: %p\n", node);
    if (node == NULL)
        return NULL;

    char fn[300];
    sprintf(fn, "/c/%s", node->header.fileName);
    printf("Open file: %s\n", fn);
    FILE *fp = fopen(fn, "wb+");
    printf("File pointer: %p\n", fp);
    return fp;
}

static duk_ret_t gluePollDebugEvent(duk_context *ctx)
{
    int32_t ret;
    int32_t uart_num;
    bridge_packet_t *packet;
    duk_int_t duk_ret;
    char *jsbuff;
    uint8_t buff[4];

    uart_num = duk_to_int32(ctx, 0);

    packet = halBridgeFetchPacket(uart_num);
    switch (packet->opCode)
    {
    case OP_EXEC_CMD:
        jsbuff = (char *)malloc(packet->dataSize + 1);
        memset(jsbuff, 0, packet->dataSize + 1);
        memcpy(jsbuff, packet->data, packet->dataSize);
        duk_ret = duk_peval_string(ctx, jsbuff);
        char *msg;

        if (duk_ret != 0)
        {
            msg = duk_safe_to_stacktrace(ctx, -1);
        }
        else
        {
            msg = duk_safe_to_string(ctx, -1);
        }

        bridgr_response_t response = {
            .magic = RESULT_MAGIC,
            .resultType = RESULT_EXEC_CMD,
            .dataSize = strlen(msg),
            .data = (uint8_t *)msg};
        halBridgeResponse(uart_num, &response);

        duk_pop(ctx);

        break;
    case OP_SEND_FILE_HEADER:
    {
        file_header_t *header = (file_header_t *)malloc(sizeof(file_header_t));

        memset(buff, 0, 4);
        memcpy(&buff[0], packet->data, 2);
        header->uniqueId = U16LE(buff);

        memset(buff, 0, 4);
        memcpy(&buff[0], packet->data + 2, 4);
        header->fileSize = U32LE(buff);

        memset(buff, 0, 4);
        memcpy(&buff[0], packet->data + 6, 2);
        header->fileNameSize = U16LE(buff);

        header->fileName = (char *)malloc(header->fileNameSize + 1);
        memset(header->fileName, 0, header->fileNameSize + 1);
        memcpy(header->fileName, (packet->data + 8), header->fileNameSize);
        addRecord(header);

        msg = "Start file transmit";
        bridgr_response_t response = {
            .magic = RESULT_MAGIC,
            .resultType = RESULT_FILE_HEADER,
            .dataSize = strlen(msg),
            .data = (uint8_t *)msg};
        halBridgeResponse(uart_num, &response);
        break;
    }
    case OP_SEND_FILE_PACKET:
    {
        file_data_t data;

        memset(buff, 0, 4);
        memcpy(&buff[0], packet->data, 2);
        data.uniqueId = U16LE(buff);

        memset(buff, 0, 4);
        memcpy(&buff[0], packet->data + 2, 4);
        data.offset = U32LE(buff);

        memset(buff, 0, 4);
        memcpy(&buff[0], packet->data + 6, 4);
        data.size = U32LE(buff);

        printf("\n\nUID: %d\noffset: %d\nsize: %d\n", data.uniqueId, data.offset, data.size);
        FILE *fp = openRecord(data.uniqueId);
        if (fp != NULL)
        {
            printf("Write file...\n");
            fseek(fp, data.offset, SEEK_SET);
            fwrite(packet->data + 10, 1, data.size, fp);
            fflush(fp);
            fclose(fp);
        }
        break;
    }
    case OP_SEND_FILE_END:
    {
        memset(buff, 0, 4);
        memcpy(&buff[0], packet->data, 2);
        uint16_t uid = U16LE(buff);
        removeRecord(uid);

        msg = "File transmit finished";
        bridgr_response_t response = {
            .magic = RESULT_MAGIC,
            .resultType = RESULT_FILE_END,
            .dataSize = strlen(msg),
            .data = (uint8_t *)msg};
        halBridgeResponse(uart_num, &response);
        break;
    }
    default:
        break;
    }

    free(packet->data);
    free(packet);

    return 1;
}

/* method */
const duk_function_list_entry module_bridge_methods[] = {
    {"pollDebugEvent", gluePollDebugEvent, 1},
    {NULL, NULL, 0}};

void module_bridge_init(duk_context *ctx)
{
    duk_push_object(ctx);

    duk_put_function_list(ctx, -1, module_bridge_methods);

    duk_put_global_string(ctx, "bridge");
}